import json
import secrets
import string
from subprocess import getstatusoutput
from flask import current_app, jsonify

from messenger import redis_client
from messenger.utils.redis_util import RedisKey
from messenger.utils.shell import ShellCmdApi
from messenger.utils.bash import (
    pxe_boot,
    power_on_off,
    pmachine_reset_password,
    get_bmc_user_id,
    reset_bmc_user_passwd,
    ResourceQueryCli
)
from messenger.utils.pxe import PxeInstall, CheckInstall
from messenger.utils.response_util import RET
from messenger.utils.requests_util import update_request
from messenger.utils.pssh import ConnectionApi
from messenger.utils.pmachine_balance_util import Pmachine
from messenger.schema.pmachine import CaculateCoef


class AutoInstall:
    def __init__(self, body):
        self.pmachine = json.loads(body.get("pmachine"))
        self.mirroring = json.loads(body.get("mirroring"))

    def kickstart(self):
        if not self.mirroring["efi"]:
            return jsonify(
                {
                    "error_code": RET.INSTALL_CONF_ERR,
                    "error_msg": "The milestone image does not provide grub.efi path .",
                }
            )

        if not self.pmachine["mac"]:
            return jsonify(
                {
                    "error_code": RET.INSTALL_CONF_ERR,
                    "error_msg": "The physical machine registration information does not exist in the mac address.",
                }
            )

        if not self.pmachine["ip"]:
            return jsonify(
                {
                    "error_code": RET.INSTALL_CONF_ERR,
                    "error_msg": "The registration information of the physical machine does not have an IP address.",
                }
            )

        result = PxeInstall(
            self.pmachine["mac"],
            self.pmachine["ip"],
            self.mirroring["efi"]
        ).bind_efi_mac_ip()

        if isinstance(result, tuple):
            return result

        exitcode, output = ShellCmdApi(
            pxe_boot(
                self.pmachine["bmc_ip"],
                self.pmachine["bmc_user"],
                self.pmachine["bmc_password"],
            )
        ).exec()

        if exitcode:
            error_msg = (
                    "Failed to boot pxe to start the physical machine:%s."
                    % self.pmachine["ip"]
            )
            current_app.logger.error(error_msg)
            current_app.logger.error(output)

            return jsonify(
                error_code=RET.INSTALL_CONF_ERR,
                error_msg=error_msg
            )

        result = CheckInstall(self.pmachine["ip"]).check()

        if isinstance(result, tuple):
            return result

        return jsonify(
            error_code=RET.OK,
            error_msg="os install succeed"
        )


class OnOff:
    def __init__(self, body) -> None:
        self._body = body
        self.pmachine = body.get("pmachine")

    def on_off(self):
        exitcode, output = ShellCmdApi(
            power_on_off(
                self.pmachine["bmc_ip"],
                self.pmachine["bmc_user"],
                self.pmachine["bmc_password"],
                self._body.get("status"),
            )
        ).exec()

        if exitcode:
            return jsonify(error_code=exitcode, error_msg=output)

        exitcode, output = getstatusoutput(
            "ipmitool -I lanplus -H %s -U %s  -P %s power status"
            % (self.pmachine["bmc_ip"], self.pmachine["bmc_user"], self.pmachine["bmc_password"])
        )
        if exitcode != 0:
            raise ValueError("The infomation of BMC provided is wrong.")

        return update_request(
            "/api/v1/pmachine/{}".format(
                self.pmachine.get("id")
            ),
            {
                "status": output.split()[-1]
            },
            self._body.get("auth")
        )


class PmachineWeight:
    def __init__(self, body) -> None:
        self._body = body

    def init_weight(self):
        auth = self._body.pop("auth")
        _e_min = self._body.pop("e_min")
        _i_min = self._body.pop("i_min")
        _i_max = self._body.pop("i_max")
        _w_rest = self._body.pop("w_rest")
        _total_w = self._body.pop("total_w")
        if self._body.get("description") == current_app.config.get("CI_HOST"):
            ssh = ConnectionApi(
                ip=self._body.get("ip"),
                port=self._body.get("port"),
                user=self._body.get("user"),
                passwd=self._body.get("password"),
            )

            conn = ssh.conn()
            if not conn:
                return jsonify(
                    {
                        "error_code": RET.VERIFY_ERR,
                        "error_msg": "Failed to connect to physical machine.",
                    }
                )
            exitcode, output = ShellCmdApi(
                ResourceQueryCli.mem_total_cli,
                ssh
            ).exec()

            if exitcode:
                return jsonify(
                    {
                        "error_code": RET.BASH_ERROR,
                        "error_msg": "bash execute error:{}".format(output)
                    }
                )

            _w_solid = round(
                current_app.config.get("VIRTUALIZE_MEMORY_MAX_RADIO")
                * float(output) /
                current_app.config.get("VIRTUALIZE_MEMORY_STANDARD_SIZE"),
                2
            )
            calculate_coef = CaculateCoef(_e_min, _i_min, _i_max, _w_rest)
            alpha, beta, machine_w_current = Pmachine(calculate_coef, w_solid=_w_solid).init_machine_weight_info
            self._body.update({
                "alpha": alpha,
                "beta": beta,
                "w_solid": _w_solid,
                "fail_num": 0
            })
            redis_client.set(
                RedisKey.pmachine_ip(
                    self._body.get("machine_group_id"),
                    self._body.get("ip")
                ),
                machine_w_current
            )
            _total_w = _total_w + _w_solid

            try:
                _ = update_request(
                    "/api/v1/machine-group/{}".format(self._body.get("machine_group_id")),
                    {
                        "total_w": _total_w
                    },
                    auth
                )

                _resp = update_request(
                    "/api/v1/pmachine/{}".format(self._body.get("id")),
                    self._body,
                    auth
                )
                return _resp
            except RuntimeError as e:
                current_app.logger.error(e)
        else:
            return update_request(
                "/api/v1/pmachine/{}".format(self._body.get("id")),
                self._body,
                auth
            )


class PmachineSshPassword:
    def __init__(self, body) -> None:
        self._body = body

    def reset_password(self):
        _total_w = self._body.pop("total_w")
        _w_solid = self._body.pop("w_solid")
        _description = self._body.pop("description")
        if self._body.get("random_flag"):
            random_password = "".join(
                [secrets.choice(string.ascii_letters) for _ in range(3)]
                + [secrets.choice(string.digits) for _ in range(3)]
                + [secrets.choice(current_app.config.get("RANDOM_PASSWORD_CHARACTER")) for _ in range(2)]
            )

            new_password = random_password
            ssh = ConnectionApi(
                ip=self._body.get("ip"),
                port=self._body.get("port"),
                user=self._body.get("user"),
                passwd=self._body.get("old_password"),
            )
            conn = ssh.conn()
            if not conn:
                return jsonify(
                    {
                        "error_code": RET.VERIFY_ERR,
                        "error_msg": "Failed to connect to physical machine.",
                    }
                )

            exitcode, output = ShellCmdApi(
                pmachine_reset_password(
                    self._body.get("user"),
                    new_password,
                ), ssh
            ).exec()

            if exitcode:
                return jsonify(
                    {
                        "error_code": RET.BASH_ERROR,
                        "error_msg": "bash execute error:{}".format(output)
                    }
                )
        elif _description == current_app.config.get("CI_HOST"):
            new_password = self._body.get("password")
            _total_w = _total_w - _w_solid
            try:
                _ = update_request(
                    "/api/v1/machine-group/{}".format(self._body.get("machine_group_id")),
                    {
                        "total_w": _total_w
                    },
                    self._body.get("auth")
                )
            except RuntimeError as e:
                current_app.logger.error(e)
        else:
            new_password = self._body.get("password")

        redis_client.delete(
            RedisKey.pmachine_ip(
                self._body.get("machine_group_id"),
                self._body.get("ip")
            )
        )
        return update_request(
            "/api/v1/pmachine/{}".format(self._body.get("id")),
            {
                "password": new_password
            },
            self._body.get("auth")
        )


class PmachineBmcPassword:
    def __init__(self, body) -> None:
        self._body = body

    def reset_bmc_password(self):
        ssh = ConnectionApi(
            ip=self._body.get("ip"),
            port=self._body.get("port"),
            user=self._body.get("bmc_user"),
            passwd=self._body.get("old_bmc_password"),
        )
        conn = ssh.conn()
        if not conn:
            return jsonify(
                {
                    "error_code": RET.VERIFY_ERR,
                    "error_msg": "Failed to connect to physical machine.",
                }
            )

        exitcode, output = ShellCmdApi(
            get_bmc_user_id(
                self._body.get("bmc_user"),
            ), ssh
        ).exec()

        if exitcode:
            return jsonify(
                error_code=exitcode,
                error_msg=output)

        exitcode, output = ShellCmdApi(
            reset_bmc_user_passwd(
                output,
                self._body.get("bmc_password"),
            ), ssh
        ).exec()

        if exitcode:
            return jsonify(
                error_code=exitcode,
                error_msg=output)

        return update_request(
            "/api/v1/pmachine/{}".format(self._body.get("id")),
            {
                "bmc_password": self._body.get("bmc_password"),
            },
            self._body.get("auth")
        )


class PmachineAsyncResultHandler:
    @staticmethod
    def edit(auth, body):
        if body.get("result") == 1:
            redis_client.set(
                RedisKey.pmachine_ip(
                    body.get("machine_group_id"),
                    body.get("ip")
                ),
                0
            )
        body.pop("result")
        _ = update_request(
            "/api/v1/pmachine/{}".format(
                body.get("id")
            ),
            body,
            auth
        )
